package calvin.study.redis.controller;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import io.micrometer.core.instrument.util.StringUtils;

@RestController
public class RedisLockController {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Value("${server.port}")
	private String serverPort;
	private final Lock lock = new ReentrantLock();

	private final String REDIS_LOCK_KEY = "redisLock";

	@GetMapping("/v1/buyGoods")
	public String v1_buyGoods() {
		try {
			if (lock.tryLock(3L, TimeUnit.SECONDS)) {
				String result = stringRedisTemplate.opsForValue().get("redislock:goods:001");
				int goodNumbers = StringUtils.isBlank(result) ? 0 : Integer.parseInt(result);
				if (goodNumbers > 0) {
					int realNumber = goodNumbers - 1;
					stringRedisTemplate.opsForValue().set("redislock:goods:001", realNumber + "");
					System.out.println("你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort);
					return "你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort;
				}
			} else {
				System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return "商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort;
	}

	@GetMapping("/v2/buyGoods")
	public String v2_buyGoods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

		Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);// setnx
		if (!lockFlag) {
			System.out.println("抢锁失败┭┮﹏┭┮" + serverPort);
		} else {
			String result = stringRedisTemplate.opsForValue().get("redislock:goods:001");
			int goodNumbers = StringUtils.isBlank(result) ? 0 : Integer.parseInt(result);
			if (goodNumbers > 0) {
				int realNumber = goodNumbers - 1;
				stringRedisTemplate.opsForValue().set("redislock:goods:001", realNumber + "");
				System.out.println("你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort);

				// 释放锁
				stringRedisTemplate.delete(REDIS_LOCK_KEY);
				return "你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort;
			} else {
				System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
			}
		}
		return "商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort;
	}

	@GetMapping("/v3/buyGoods")
	public String v3_buyGoods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
		try {

			// 设置key+过期时间，必须要合并成一行具备原子性
			Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 5L,
					TimeUnit.SECONDS);// setnx
			if (!lockFlag) {
				System.out.println("抢锁失败┭┮﹏┭┮" + serverPort);
			} else {
				String result = stringRedisTemplate.opsForValue().get("redislock:goods:001");
				int goodNumbers = StringUtils.isBlank(result) ? 0 : Integer.parseInt(result);
				if (goodNumbers > 0) {
					int realNumber = goodNumbers - 1;
					stringRedisTemplate.opsForValue().set("redislock:goods:001", realNumber + "");
					System.out.println("你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort);
					return "你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort;
				} else {
					System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
		} finally {
			// 存在非原子性问题
			if (stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY).equalsIgnoreCase(value)) {
				// 释放锁
				stringRedisTemplate.delete(REDIS_LOCK_KEY);
			}
		}
		return "商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort;
	}

	@GetMapping("/v4/buyGoods")
	public String v4_buyGoods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
		try {

			// 设置key+过期时间，必须要合并成一行具备原子性
			Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 5L,
					TimeUnit.SECONDS);// setnx
			if (!lockFlag) {
				System.out.println("抢锁失败┭┮﹏┭┮" + serverPort);
			} else {
				String result = stringRedisTemplate.opsForValue().get("redislock:goods:001");
				int goodNumbers = StringUtils.isBlank(result) ? 0 : Integer.parseInt(result);
				if (goodNumbers > 0) {
					int realNumber = goodNumbers - 1;
					stringRedisTemplate.opsForValue().set("redislock:goods:001", realNumber + "");
					System.out.println("你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort);
					return "你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort;
				} else {
					System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
		} finally {
			while (true) {
				// 开启乐观锁
				stringRedisTemplate.watch(REDIS_LOCK_KEY);

				if (stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY).equalsIgnoreCase(value)) {
					// 是否开启事务支持
					stringRedisTemplate.setEnableTransactionSupport(true);
					// 开启事务
					stringRedisTemplate.multi();
					// 执行命令
					stringRedisTemplate.delete(REDIS_LOCK_KEY);
					// 执行事务
					List<Object> list = stringRedisTemplate.exec();
					// 如果事务执行失败则重新执行,直至事务执行成功
					if (null == list) {
						continue;
					}
					// 解锁
					stringRedisTemplate.unwatch();
					break;
				}
			}
		}
		return "商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort;
	}

	@GetMapping("/v5/buyGoods")
	public String v5_buyGoods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
		try {
			// 设置key+过期时间，必须要合并成一行具备原子性
			Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 5L,
					TimeUnit.SECONDS);// setnx

			if (!lockFlag) {
				System.out.println("抢锁失败┭┮﹏┭┮" + serverPort);
			} else {
				String result = stringRedisTemplate.opsForValue().get("redislock:goods:001");
				int goodNumbers = StringUtils.isBlank(result) ? 0 : Integer.parseInt(result);
				if (goodNumbers > 0) {
					int realNumber = goodNumbers - 1;
					stringRedisTemplate.opsForValue().set("redislock:goods:001", realNumber + "");
					System.out.println("你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort);
					return "你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort;
				} else {
					System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
		} finally {
			// lua说明：先获取指定key的值，然后和传入的arg比较是否相等，相等值删除key，否则直接返回0。
			final String LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

			// 指定lua脚本，并且指定返回值类型
			DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);
			// 参数一：redisScript，参数二：key列表，参数三：arg（可多个）
			Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(REDIS_LOCK_KEY), value);
			System.out.println(result == 0 ? "非当前线程锁" : "解锁");
		}
		return "商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort;
	}

	@Autowired
	private Redisson redisson;

	@GetMapping("/v6/buyGoods")
	public String v6_buyGoods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

		RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY);
		redissonLock.lock();
		try {
			String result = stringRedisTemplate.opsForValue().get("redislock:goods:001");
			int goodNumbers = StringUtils.isBlank(result) ? 0 : Integer.parseInt(result);
			if (goodNumbers > 0) {
				int realNumber = goodNumbers - 1;
				stringRedisTemplate.opsForValue().set("redislock:goods:001", realNumber + "");
				System.out.println("你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort);
				return "你已经成功秒杀商品，此时还剩余：" + realNumber + "件" + "\t 服务器端口: " + serverPort;
			} else {
				System.out.println("商品已售罄，活动已结束" + "\t 服务器端口: " + serverPort);
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort);
		} finally {
			// 还在持有锁的状态，并且是当前线程持有的锁再解锁
			if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
				redissonLock.unlock();
			}
		}
		return "商品已经售罄/活动结束/调用超时，欢迎下次光临" + "\t 服务器端口: " + serverPort;
	}

}
